當開始評估怎設計微服務架構時, 就要來關心如何實現跨多個服務的事務(Transaction)交易了。
事務處理才能確保資料的一致性。
舉例, Order Service和Customer Service, 一個負責訂單, 一個負責客戶資料與信用額度。
兩個服務有獨立的資料庫存放各自領域的數據。
但是有些業務跨越多個服務,彼此的資料庫與連線事務也不同, 沒法用一般的事務交易方式來完成事務處理。
例如一個電商平台, 每個客戶的建立需要綁信用額度。確保每筆新訂單不會超過用戶的信用額度。
由於Order與Customer是不同服務且擁有不同的資料庫, 所以程式無法簡單的用Local的ACID事務。
同一個事務內的所有命令, 對其資料進行修改, 要就是全部完成, 或者全部不完成。發生任意錯誤可以Rollback到事務開始前的狀態。事務本身是最小工作單元, 不可分割。
MySQL是透過Undo log保存變更前的邏輯變化, 因為不是物理頁上的資料, Rollback就讀取undo log做回復。
事務在完成之前, 必須使所有資料都保持一致狀態, 事務完成後, 所有資料(包含資料庫的B樹索引、雙向鍊結表...)都必須是正確的。
資料庫允許事務併發被執行, 但多個事務間若同時操作同一筆資料, 必定會存在衝突, 事務的中間狀態可能暴露給其他事務, 導致一些事務依據其他事務的中間狀態, 把錯誤的值寫到資料庫內。
這時就需要事務的isolation, 確保事務執行不受併發事務的影響。
也是透過Undo Log和讀寫鎖來實現MVCC(多版本控管)。
Isolation讓客戶可以專注於單個事務的邏輯操作, 不用考慮race condition. 也能考慮降低一些隔離性, 提昇效能, 就是事務隔離級別。
事務隔離分為不同級別,包括未提交讀(Read uncommitted)、提交讀(Read committed)、可重複讀(Repeatable read)和串行化(Serializable)。
TritonHo的講義
事務完成之後, 對系統的修改是永久性的。
MySQL透過Redo Log, 紀錄每次操作上的物理修改。
啟動時, 不管上次資料庫是否正常關閉, 都會嘗試從Redo log中進行恢復。
所以只要Redo log有寫入, 基本上資料就是保持一致了。
因為大部分耗時的操作都在commit之前就完成了, commit指令可說是"瞬間"完成的。
MySQL默認的機制是異步複製, 就是提交的事務只要在主庫寫入binlog完成, 就會返回結果給client, 不必等待從庫是否已經接收完畢.
但這樣就是犧牲了強一致性, 因為網路傳遞也需要時間, 所以這種機制是A+P架構, 但最終資料會一致的.
就是主庫如果crash了, 寫入的場景就失敗, 自然也不會有事務提交到主庫並且複製給從庫, 所以兩邊資料是一致的;
這時把從庫變成主庫了, 大家改寫入新主庫(原先的從庫), 因為可能有些還沒複製過來的binlog, 所以這新的主庫可能資料是不完整的.
因為上圖從庫的I/O Thread跟SQL Thread其實是單線程,
但主庫允許多線程來提交事務, 所以若是主庫TPS(Transaction per second每秒事務量)很大時, 往往從庫是跟不太上的.
若是複製過來的單個事務是很多大型查詢命令時, 從庫的SQL Thread其實執行起來不快.
MySQL在主庫寫入binlog成功後, 不立刻返回結果, 而是等待任一個從庫同步完成, 才返回結果.
可是若有多個從庫, 還是無法保證全部從庫都完成同步, 所以依然不是C+P系統, 只是A+P系統.
MySQL 5.5新增了半同步複製的外掛套件 rpl_semi_sync_master
MySQL 5.7引入了rpl_semi_sync_master_wait_for_slave_count, 用來設置主庫需要等待多少個從庫的同步回應後, 才能返回結果, 預設就是1個.
Semisynchronous Replication Installation and Configuration
這些都是所謂的強一致性情境下的事務模型。
CAP理論與一致性級別於下篇介紹.
當然微服務架構下, 無論網路是否跨區, 也有2PC(Two Phase Commit)這類的事務模型.
但2PC不是個太好的選擇, 因為過程中, 會一直對資源上鎖. 且相對於單個微服務的操作時間相比非常慢.
所以很適合短事務操作, 併發較低的場景. 之後再分享SAGA事務模型.